package net.sourceforge.peers.javaxsound;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;

import net.sourceforge.peers.Logger;
import net.sourceforge.peers.media.AbstractSoundManager;
import net.sourceforge.peers.sip.Utils;

/**
 * 负责麦克风声音的读取
 * @author Mr.Yan
 */
public class JavaxSoundManager extends AbstractSoundManager {

    //声音的数据格式
    private final AudioFormat audioFormat;
    //声音的输入(麦克风)
    private TargetDataLine targetDataLine;
    //输出(音响,耳机)
    private SourceDataLine sourceDataLine;
    private final Object sourceDataLineMutex;
    private final DataLine.Info targetInfo;
    private final DataLine.Info sourceInfo;
    private FileOutputStream microphoneOutput;
    private FileOutputStream speakerInput;
    private final boolean mediaDebug;
    private final Logger logger;
    private String peersHome;
    
    public JavaxSoundManager(boolean mediaDebug, Logger logger, String peersHome) {
        this.mediaDebug = mediaDebug;
        this.logger = logger;
        this.peersHome = peersHome;
        if (peersHome == null) {
            this.peersHome = Utils.DEFAULT_PEERS_HOME;
        }
        // linear PCM 8kHz, 16 bits signed, mono-channel, little endian
        audioFormat = new AudioFormat(8000, 16, 1, true, false);
        targetInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
        sourceInfo = new DataLine.Info(SourceDataLine.class, audioFormat);
        sourceDataLineMutex = new Object();
    }

    @Override
    public void init() {
        logger.debug("openAndStartLines");
        if (mediaDebug) {
            SimpleDateFormat simpleDateFormat =
                new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
            String date = simpleDateFormat.format(new Date());
            StringBuilder buf = new StringBuilder();
            buf.append(peersHome).append(File.separator);
            buf.append(MEDIA_DIR).append(File.separator);
            buf.append(date).append("_");
            buf.append(audioFormat.getEncoding()).append("_");
            buf.append(audioFormat.getSampleRate()).append("_");
            buf.append(audioFormat.getSampleSizeInBits()).append("_");
            buf.append(audioFormat.getChannels()).append("_");
            buf.append(audioFormat.isBigEndian() ? "be" : "le");
            try {
                microphoneOutput = new FileOutputStream(buf
                        + "_microphone.output");
                speakerInput = new FileOutputStream(buf
                        + "_speaker.input");
            } catch (FileNotFoundException e) {
                logger.error("cannot create file", e);
                return;
            }
        }
        // AccessController.doPrivileged added for plugin compatibility
        AccessController.doPrivileged(
            new PrivilegedAction<Void>() {

                @Override
                public Void run() {
                    try {
                        targetDataLine = (TargetDataLine) AudioSystem.getLine(targetInfo);
                        targetDataLine.open(audioFormat);
                    } catch (LineUnavailableException e) {
                        logger.error("target line unavailable", e);
                        return null;
                    } catch (SecurityException e) {
                        logger.error("security exception", e);
                        return null;
                    } catch (Throwable t) {
                        logger.error("throwable " + t.getMessage());
                        return null;
                    }
                    //启动麦克风准备录音
                    targetDataLine.start();
                    synchronized (sourceDataLineMutex) {
                        try {
                            sourceDataLine = (SourceDataLine) AudioSystem.getLine(sourceInfo);
                            sourceDataLine.open(audioFormat);
                        } catch (LineUnavailableException e) {
                            logger.error("source line unavailable", e);
                            return null;
                        }
                        //扬声器输出
                        sourceDataLine.start();
                    }
                    return null;
                }
        });

    }

    @Override
    public synchronized void close() {
        logger.debug("closeLines");
        if (microphoneOutput != null) {
            try {
                microphoneOutput.close();
            } catch (IOException e) {
                logger.error("cannot close file", e);
            }
            microphoneOutput = null;
        }
        if (speakerInput != null) {
            try {
                speakerInput.close();
            } catch (IOException e) {
                logger.error("cannot close file", e);
            }
            speakerInput = null;
        }
        // AccessController.doPrivileged added for plugin compatibility
        AccessController.doPrivileged(new PrivilegedAction<Void>() {

            @Override
            public Void run() {
                if (targetDataLine != null) {
                    targetDataLine.close();
                    targetDataLine = null;
                }
                synchronized (sourceDataLineMutex) {
                    if (sourceDataLine != null) {
                        sourceDataLine.drain();
                        sourceDataLine.stop();
                        sourceDataLine.close();
                        sourceDataLine = null;
                    }
                }
                return null;
            }
        });
    }

    @Override
    public synchronized byte[] readData() {
        if (targetDataLine == null) {
            return null;
        }

        //获取应用程序当前可用于数据线内部缓冲区中处理数据的字节数。
        // 对于源数据行，这是可以写入缓冲区而不阻塞的数据量。 对于目标数据行，这是应用程序可以读取的数据量
        int ready = targetDataLine.available();
        while (ready == 0) {
            try {
                Thread.sleep(2);
                ready = targetDataLine.available();
            } catch (InterruptedException e) {
                return null;
            }
        }
        if (ready <= 0) {
            return null;
        }
        byte[] buffer = new byte[ready];
        targetDataLine.read(buffer, 0, buffer.length);
        if (mediaDebug) {
            try {
                microphoneOutput.write(buffer, 0, buffer.length);
            } catch (IOException e) {
                logger.error("cannot write to file", e);
                return null;
            }
        }
        return buffer;
    }

    @Override
    public void writeData(byte[] buffer, int offset, int length) {
        int numberOfBytesWritten;
        synchronized (sourceDataLineMutex) {
            if (sourceDataLine == null) {
                return;
            }
            numberOfBytesWritten = sourceDataLine.write(buffer, offset, length);
        }
        if (mediaDebug) {
            try {
                speakerInput.write(buffer, offset, numberOfBytesWritten);
            } catch (IOException e) {
                logger.error("cannot write to file", e);
            }
        }
    }

}
